﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace WhomaShuriken.Core.Utility
{
    /// <summary>
    /// IP処理
    /// </summary>
    public class Ip
    {

        //変数
        /// <summary>
        /// ipV4体系をListで持つ
        /// </summary>
        private List<int> ipV4 = new List<int> { 0, 0, 0, 0 };
        /// <summary>
        /// IPv6体系をListで持つ
        /// </summary>
        private List<UInt16> ipV6 = new List<UInt16> { 0, 0, 0, 0, 0, 0, 0, 0 };

        //プロパティ・変数
        /// <summary>
        /// IP種別
        /// </summary>
        public INET_KIND InetKind { get; private set; }
        /// <summary>
        /// IPv4
        /// </summary>
        public List<int> IpV4 { get { return ipV4; } }//00-ff 0-255
        /// <summary>
        /// Ipv6
        /// </summary>
        public List<UInt16> IpV6 { get { return ipV6; } }//0000-ffff 0-65535
        /// <summary>
        /// 待ち受け状態格納用プロパティ
        /// </summary>
        public bool Any { get; private set; }
        /// <summary>
        /// 状態格納用プロパティ
        /// </summary>
        public bool Status { get; private set; }
        /// <summary>
        /// IPアドレス保管用Byte配列
        /// </summary>
        public byte[] Data
        {
            get
            {
                if (InetKind == INET_KIND.V4)
                {//IPv4
                    byte[] tmp = new byte[4];
                    tmp[3] = (byte)ipV4[3];
                    tmp[2] = (byte)ipV4[2];
                    tmp[1] = (byte)ipV4[1];
                    tmp[0] = (byte)ipV4[0];
                    return tmp;
                }
                else
                {//IPv6

                    byte[] tmp = new byte[16];
                    byte[] b = BitConverter.GetBytes(Util.htonl(AddrV6_h));
                    Buffer.BlockCopy(b, 0, tmp, 0, 8);
                    b = BitConverter.GetBytes(Util.htonl(AddrV6_l));
                    Buffer.BlockCopy(b, 0, tmp, 8, 8);
                    return tmp;
                }
            }
        }


        /// <summary>
        /// デフォルト値の初期化
        /// </summary>
        /// <param name="inetKind">利用するIP種別</param>
        void _init(INET_KIND inetKind)
        {
            this.InetKind = inetKind;
            for (int i = 0; i < 4; i++)
                ipV4[i] = 0;
            for (int i = 0; i < 8; i++)
                ipV6[i] = 0;
            Any = false;
        }
        /// <summary>
        /// インスタンスコンストラクタ
        /// </summary>
        public Ip()
        {


            _init(INET_KIND.V4);//デフォルト値での初期化
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="ipStr">IP条件句</param>
        public Ip(string ipStr)
        {

            _init(INET_KIND.V4);//デフォルト値での初期化

            if (ipStr == "INADDR_ANY")
            {//IPV4
                Any = true;
            }
            else if (ipStr == "IN6ADDR_ANY_INIT")
            {//IPV6
                InetKind = INET_KIND.V6;
                Any = true;
            }
            else if (ipStr.IndexOf('.') > 0)
            {//IPV4
                //Ver5.0.0-a11 名前で指定された場合は、例外に頼らずここで処理する（高速化）
                foreach (char c in ipStr)
                {
                    if (c != '.' && (c < '0' || '9' < c))
                        goto err;

                }


                string[] tmp = ipStr.Split('.');

                _init(INET_KIND.V4);//デフォルト値での初期化

                try
                {
                    if (tmp.Length == 4)
                    {

                        for (int i = 0; i < 4; i++)
                        {
                            ipV4[i] = Convert.ToInt32(tmp[i]);
                        }
                    }
                    else if (tmp.Length == 3)
                    {//ネットアドレスでnewされた場合
                        for (int i = 0; i < 3; i++)
                            ipV4[i] = Convert.ToInt32(tmp[i]);
                        ipV4[3] = 0;
                    }
                    else
                    {
                        goto err;
                    }
                }
                catch
                {
                    goto err;
                }
                for (int i = 0; i < 4; i++)
                {
                    if (ipV4[i] < 0 || 255 < ipV4[i])
                        goto err;
                }

            }
            else if (ipStr.IndexOf(':') >= 0)
            {//IPV6

                _init(INET_KIND.V6);//デフォルト値での初期化

                //Ver5.1.2 もし、[xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]で囲まれている場合は、除去する
                string[] tmp = ipStr.Split(new char[] { '[', ']' });
                if (tmp.Length == 3)
                {
                    ipStr = tmp[1];
                }

                tmp = ipStr.Split(':');

                int n = ipStr.IndexOf("::");
                if (0 <= n)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append(ipStr.Substring(0, n));
                    for (int i = tmp.Length; i < 8; i++)
                        sb.Append(":");
                    sb.Append(ipStr.Substring(n));
                    tmp = sb.ToString().Split(':');
                }
                if (tmp.Length != 8)
                    goto err;

                try
                {

                    if (tmp.Length == 8)
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            if (tmp[i] == "")
                            {
                                ipV6[i] = 0;
                            }
                            else
                            {
                                try
                                {
                                    ipV6[i] = Convert.ToUInt16(tmp[i], 16);
                                }
                                catch
                                {
                                    goto err;
                                }
                            }
                        }
                        //} else if (tmp.Length == 3) {//ネットアドレスでnewされた場合
                    }
                    else
                    {
                        goto err;
                    }
                }
                catch
                {
                    goto err;
                }
                for (int i = 0; i < 8; i++)
                {
                    if (ipV6[i] < 0 || 65535 < ipV6[i])
                    {
                        goto err;
                    }
                }
            }
            else
            {
                goto err;
            }
            Status = true;//初期化成功
            return;
        err:
            _init(InetKind);//デフォルト値での初期化
        }
        /// <summary>
        /// uInt化した値からIPを求めるコンストラクタ(IPv4)
        /// </summary>
        /// <param name="ip">マスクを含めた2進数から変換したIPアドレス</param>
        public Ip(uint ip)
        {


            _init(INET_KIND.V4);//デフォルト値での初期化

            byte[] tmp = BitConverter.GetBytes(ip);
            for (int i = 0; i < 4; i++)
                ipV4[i] = tmp[3 - i];
            if (ipV4[0] == 0 && ipV4[1] == 0 && ipV4[2] == 0 && ipV4[3] == 0)
            {
                Any = true;
            }
            Status = true;//初期化成功
        }
        /// <summary>
        /// uInt化した値からIPを求めるコンストラクタ(IPv6)
        /// </summary>
        /// <param name="h">上位32bit</param>
        /// <param name="l">下位32bit</param>
        public Ip(UInt64 h, UInt64 l)
        {

            _init(INET_KIND.V6);//デフォルト値での初期化

            byte[] b = BitConverter.GetBytes(h);
            for (int i = 0; i < 4; i++)
            {
                ipV6[3 - i] = BitConverter.ToUInt16(b, i * 2);
            }
            b = BitConverter.GetBytes(l);
            for (int i = 0; i < 4; i++)
            {
                ipV6[3 - i + 4] = BitConverter.ToUInt16(b, i * 2);
            }
            Status = true;//初期化成功
        }

        //Ver5.0.0-a3 2009.09.08 黒田さんから情報提供頂きました
        /// <summary>
        /// IPアドレスを返す
        /// </summary>
        /// <returns>IPアドレス</returns>
        override public string ToString()
        {
            return IpStr;
        }

        //Ver5.0.0-a3　錯誤が生じる可能性が高いのでprivateに変更
        //外部からはToString()を使用する
        private string IpStr
        {
            get
            {
                if (InetKind == INET_KIND.V4)
                {
                    if (Any)
                        return "INADDR_ANY";
                    return string.Format("{0}.{1}.{2}.{3}", ipV4[0], ipV4[1], ipV4[2], ipV4[3]);
                }
                else
                {
                    if (Any)
                        return "IN6ADDR_ANY_INIT";
                    return string.Format("{0:x}:{1:x}:{2:x}:{3:x}:{4:x}:{5:x}:{6:x}:{7:x}", ipV6[0], ipV6[1], ipV6[2], ipV6[3], ipV6[4], ipV6[5], ipV6[6], ipV6[7]);
                }
            }
        }
       
        /// <summary>
        /// uint化したIPアドレスを返す(IPv4)
        /// </summary>
        public uint AddrV4
        {
            get
            {
                if (InetKind == INET_KIND.V4)
                {
                    byte[] tmp = new byte[4];
                    tmp[3] = (byte)ipV4[0];
                    tmp[2] = (byte)ipV4[1];
                    tmp[1] = (byte)ipV4[2];
                    tmp[0] = (byte)ipV4[3];
                    return BitConverter.ToUInt32(tmp, 0);
                }
                else
                {
                    return 0;
                }
            }
        }
        /// <summary>
        /// uint化したIPアドレスの上位部を返す(IPv6)
        /// </summary>
        public UInt64 AddrV6_h
        {
            get
            {
                if (InetKind == INET_KIND.V6)
                {
                    byte[] tmp = new byte[8];
                    for (int i = 0; i < 4; i++)
                    {
                        byte[] b = BitConverter.GetBytes(ipV6[3 - i]);
                        Buffer.BlockCopy(b, 0, tmp, i * 2, 2);
                    }
                    return BitConverter.ToUInt64(tmp, 0);
                }
                else
                {
                    return 0;
                }
            }
        }
        /// <summary>
        /// uint化したIPアドレスの下位部を返す(IPv6)
        /// </summary>
        public UInt64 AddrV6_l
        {
            get
            {
                if (InetKind == INET_KIND.V6)
                {
                    byte[] tmp = new byte[8];
                    for (int i = 0; i < 4; i++)
                    {
                        byte[] b = BitConverter.GetBytes(ipV6[7 - i]);
                        Buffer.BlockCopy(b, 0, tmp, i * 2, 2);
                    }
                    return BitConverter.ToUInt64(tmp, 0);
                }
                else
                {
                    return 0;
                }
            }
        }
      
        /// <summary>
        /// IPAddressクラスとしてIPを返す
        /// </summary>
        public IPAddress IPAddress
        {
            get
            {
                if (InetKind == INET_KIND.V4)
                {
                    if (Any)
                        return IPAddress.Any;
                    return IPAddress.Parse(IpStr);
                }
                else
                {
                    if (Any)
                        return IPAddress.IPv6Any;
                    return IPAddress.Parse(IpStr);
                }
            }
        }
        /// <summary>
        /// IPアドレスの比較を行う
        /// </summary>
        /// <param name="d">比較対象IPクラス</param>
        /// <returns>比較対象よりも等しければ0　大きければ1　小さければ-1</returns>
        public int Comp(Ip d)
        {
            if (InetKind == INET_KIND.V4)
            {//IPV4
                for (int i = 0; i < 4; i++)
                {
                    if (ipV4[i] != d.ipV4[i])
                        return (ipV4[i] > d.ipV4[i]) ? 1 : -1;
                }
                /*if (ipV4[0] != d.ipV4[0]) {
                    return (ipV4[0] > d.ipV4[0]) ? 1 : -1;
                } else {
                    if (ipV4[1] != d.ipV4[1]) {
                        return (ipV4[1] > d.ipV4[1]) ? 1 : -1;
                    } else {
                        if (ipV4[2] != d.ipV4[2]) {
                            return (ipV4[2] > d.ipV4[2]) ? 1 : -1;
                        } else {
                            if (ipV4[3] != d.ipV4[3]) {
                                return (ipV4[3] > d.ipV4[3]) ? 1 : -1;
                            }
                        }
                    }
                }*/
            }
            else
            {//IPV6
                for (int i = 0; i < 8; i++)
                {
                    if (ipV6[i] != d.ipV6[i])
                        return (ipV6[i] > d.ipV6[i]) ? 1 : -1;
                }
                /*if (ipV6[0] != d.ipV6[0]) {
                    return (ipV6[0] > d.ipV6[0]) ? 1 : -1;
                } else {
                    if (ipV6[1] != d.ipV6[1]) {
                        return (ipV6[1] > d.ipV6[1]) ? 1 : -1;
                    } else {
                        if (ipV6[2] != d.ipV6[2]) {
                            return (ipV6[2] > d.ipV6[2]) ? 1 : -1;
                        } else {
                            if (ipV6[3] != d.ipV6[3]) {
                                return (ipV6[3] > d.ipV6[3]) ? 1 : -1;
                            } else {
                                if (ipV6[4] != d.ipV6[4]) {
                                    return (ipV6[4] > d.ipV6[4]) ? 1 : -1;
                                } else {
                                    if (ipV6[5] != d.ipV6[5]) {
                                        return (ipV6[5] > d.ipV6[5]) ? 1 : -1;
                                    } else {
                                        if (ipV6[6] != d.ipV6[6]) {
                                            return (ipV6[6] > d.ipV6[6]) ? 1 : -1;
                                        } else {
                                            if (ipV6[7] != d.ipV6[7]) {
                                                return (ipV6[7] > d.ipV6[7]) ? 1 : -1;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }*/
            }
            return 0;
        }
        /// <summary>
        /// IPクラスの同一性を評価する
        /// </summary>
        /// <param name="obj">IPクラス</param>
        /// <returns>同一ならばTrue 違うならばFalse</returns>
        public override bool Equals(object obj){
            var tarIP = obj as Ip;
            if(tarIP == null) return false;
            
            if(this.InetKind == INET_KIND.V4){
            
                return (this.AddrV4 == tarIP.AddrV4
                     && this.Any == tarIP.Any);
            }else if(this.InetKind == INET_KIND.V6){
            
                return (this.AddrV4 == tarIP.AddrV4 
                 && this.AddrV6_h == tarIP.AddrV6_h
                 && this.AddrV6_l == tarIP.AddrV6_l
                 && this.Any == tarIP.Any);
            }else return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode(){

            return base.GetHashCode();
        }
    }
}
